#include <string>
#include <iostream>
#include <fstream>
#include <glm\glm.hpp>
#include <glm\gtc\type_ptr.hpp> // glm::value_ptr
#include <glm\gtc\matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale, glm::persp

using namespace std;

struct Utils
{
    static string readShaderFile(const char *filePath);
    static void printShaderLog(GLuint shader);
    static void printProgramLog(int prog);
    static GLuint prepareShader(int shaderTYPE, const char *shaderPath);
    static int finalizeShaderProgram(GLuint sprogram);

    static bool checkOpenGLError();

    static GLuint createShaderProgram(const char *vp, const char *fp);
    static GLuint createShaderProgram(const char *vp, const char *gp, const char *fp);
    static GLuint createShaderProgram(const char *vp, const char *tCS, const char *tES, const char *fp);
    static GLuint createShaderProgram(const char *vp, const char *tCS, const char *tES, char *gp, const char *fp);
};

string Utils::readShaderFile(const char *filePath)
{
    string content;
    ifstream fileStream(filePath, ios::in);
    string line = "";
    while (!fileStream.eof())
    {
        getline(fileStream, line);
        content.append(line + "\n");
    }
    fileStream.close();
    return content;
}

bool Utils::checkOpenGLError()
{
    bool foundError = false;
    int glErr = glGetError();
    while (glErr != GL_NO_ERROR)
    {
        cout << "glError: " << glErr << endl;
        foundError = true;
        glErr = glGetError();
    }
    return foundError;
}

void Utils::printShaderLog(GLuint shader)
{
    int len = 0;
    int chWrittn = 0;
    char *log;
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
    if (len > 0)
    {
        log = (char *)malloc(len);
        glGetShaderInfoLog(shader, len, &chWrittn, log);
        cout << "Shader Info Log: " << log << endl;
        free(log);
    }
}

GLuint Utils::prepareShader(int shaderTYPE, const char *shaderPath)
{
    GLint shaderCompiled;
    string shaderStr = readShaderFile(shaderPath);
    const char *shaderSrc = shaderStr.c_str();
    GLuint shaderRef = glCreateShader(shaderTYPE);
    glShaderSource(shaderRef, 1, &shaderSrc, NULL);
    glCompileShader(shaderRef);
    checkOpenGLError();
    glGetShaderiv(shaderRef, GL_COMPILE_STATUS, &shaderCompiled);
    if (shaderCompiled != 1)
    {
        if (shaderTYPE == 35633)
            cout << "Vertex ";
        if (shaderTYPE == 36488)
            cout << "Tess Control ";
        if (shaderTYPE == 36487)
            cout << "Tess Eval ";
        if (shaderTYPE == 36313)
            cout << "Geometry ";
        if (shaderTYPE == 35632)
            cout << "Fragment ";
        cout << "shader compilation error." << endl;
        printShaderLog(shaderRef);
    }
    return shaderRef;
}

void Utils::printProgramLog(int prog)
{
    int len = 0;
    int chWrittn = 0;
    char *log;
    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len);
    if (len > 0)
    {
        log = (char *)malloc(len);
        glGetProgramInfoLog(prog, len, &chWrittn, log);
        cout << "Program Info Log: " << log << endl;
        free(log);
    }
}

int Utils::finalizeShaderProgram(GLuint sprogram)
{
    GLint linked;
    glLinkProgram(sprogram);
    checkOpenGLError();
    glGetProgramiv(sprogram, GL_LINK_STATUS, &linked);
    if (linked != 1)
    {
        cout << "linking failed" << endl;
        printProgramLog(sprogram);
    }
    return sprogram;
}

GLuint Utils::createShaderProgram(const char *vp, const char *fp)
{
    GLuint vShader = prepareShader(GL_VERTEX_SHADER, vp);
    GLuint fShader = prepareShader(GL_FRAGMENT_SHADER, fp);
    GLuint vfprogram = glCreateProgram();
    glAttachShader(vfprogram, vShader);
    glAttachShader(vfprogram, fShader);
    finalizeShaderProgram(vfprogram);
    return vfprogram;
}